/**
 * This file is part of Sonedyan.
 * 
 * Sonedyan is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation;
 * either version 3 of the License, or (at your option) any
 * later version.
 *
 * Sonedyan is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Octave; see the file COPYING.  If not
 * see <http://www.gnu.org/licenses/>.
 * 
 * Copyright (C) 2009-2012 Jimmy Dubuisson <jimmy.dubuisson@gmail.com>
 */

package org.unige.mpej.eckmann.sonedyan.fns.graph;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;

import org.apache.log4j.Logger;
import org.unige.mpej.eckmann.sonedyan.fns.bean.Actor;
import org.unige.mpej.eckmann.sonedyan.fns.bean.Link;
import org.unige.mpej.eckmann.sonedyan.fns.bean.Actor.Status;
import org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics.GraphMetric;
import org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics.NodeMetric;
import org.unige.mpej.eckmann.sonedyan.utils.DataUtils;
import org.unige.mpej.eckmann.sonedyan.utils.GraphUtils;
import org.unige.mpej.eckmann.sonedyan.utils.Statistics;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;

/**
 * Set of methods for measuring FNS graph properties
 */
public class GraphMetrics
{
	private static Logger log = Logger.getLogger(org.unige.mpej.eckmann.sonedyan.fns.graph.GraphMetrics.class);
	
	/**
	 * get the set of nodes specified by their status
	 * 
	 * NB: a given vertex might have different status in which case a message is logged!
	 */
	public static Set<String> getStatusNodes(Graph<Actor, Link> graph, Status status)
	{
		HashSet<String> set = new HashSet<String>();
		Collection<Link> edges = graph.getEdges();
		
		for (Link e : edges)
		{
			// if 'from' vertex of the current edge has the specified status
			if (!set.contains(e.getFromActorId()))
			{
				if (e.getFromStatus() == status)
				{
					set.add(e.getFromActorId());
				}
			}
			else
			{
				if (e.getFromStatus() != status)
				{
					log.info("Node '" + e.getFromActorId() + "' has status '" + status + "' and '" + e.getFromStatus()+ "'" );
				}
			}
			
			// if 'to' vertex of the current edge has the specified status
			if (!set.contains(e.getToActorId()))
			{
				if (e.getToStatus() == status)
				{
					set.add(e.getToActorId());
				}
			}
			else
			{
				if (e.getToStatus() != status)
				{
					log.info("Node " + e.getToActorId() + "has status '" + status + "' and '" + e.getToStatus()+ "'" );
				}
			}
		}
		
		return set;
	}
	
	/**
	 * get node status (potentially many)
	 */
	public static Vector<Status> getNodeStatus(Graph<Actor, Link> graph, String nodeId)
	{
		Vector<Status> status = new Vector<Status>();
		Collection<Link> edges = graph.getEdges();
		
		for (Link e : edges)
		{
			if (e.getFromActorId().equals(nodeId))
			{
				if (status.size() == 0)
				{
					status.add(e.getFromStatus());
				}
				else
				{
					if (!status.contains(e.getFromStatus()))
					{
						status.add(e.getFromStatus());
					}
				}
			}
			else if (e.getToActorId().equals(nodeId))
			{
				if (status.size() == 0)
				{
					status.add(e.getToStatus());
				}
				else
				{
					if (!status.contains(e.getToStatus()))
					{
						status.add(e.getToStatus());
					}
				}
			}
		}
		
		if (status.size() != 0)
			return status;
		else
			return null;
	}
	
	/**
	 * get the correlation between 
	 */
	public static Vector<Double> getTransitionNodesCorrelation(Hashtable<String, TreeMap<Date, Double>> timeSeries, Vector<Double> avgStatusTimeSeries, Hashtable<String, Integer> transitionIndexes, int lag)
	{
		try
		{
			Set<String> ids = transitionIndexes.keySet();
			Vector<Double> beforeAfter = new Vector<Double>();
			
			int counter = 0;
			double beforeTotal = 0;
			double afterTotal = 0;
			
			for (String id : ids)
			{
				TreeMap<Date, Double> nodeTimeSeries = timeSeries.get(id);
				Vector<Double> nodeTimeSeriesValues = new Vector<Double>(nodeTimeSeries.values());
				int transitionIndex = transitionIndexes.get(id);
				int seriesSize = nodeTimeSeriesValues.size();
				
				Vector<Double> beforeSeries1 = new Vector<Double>();
				Vector<Double> beforeSeries2 = new Vector<Double>();
				
				Vector<Double> afterSeries1 = new Vector<Double>();
				Vector<Double> afterSeries2 = new Vector<Double>();
				
				if (transitionIndex >= lag && (transitionIndex + lag) <= seriesSize)
				{
					for (int i = (transitionIndex -lag); i < transitionIndex; i++)
					{
						beforeSeries1.add(nodeTimeSeriesValues.get(i));
						beforeSeries2.add(avgStatusTimeSeries.get(i));
					}
					
					for (int i = transitionIndex; i < (transitionIndex + lag); i++)
					{
						afterSeries1.add(nodeTimeSeriesValues.get(i));
						afterSeries2.add(avgStatusTimeSeries.get(i));
					}
					
					beforeTotal += Statistics.sampleCorrelation(beforeSeries1, beforeSeries2);
					afterTotal += Statistics.sampleCorrelation(afterSeries1, afterSeries2);
					counter++;
				}
			}
			
			beforeAfter.add(beforeTotal / counter);
			beforeAfter.add(afterTotal / counter);
			
			return beforeAfter;
		}
		catch(Exception e)
		{
			log.error("An error occured: " + e.getMessage());
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * get the average metric value of the status nodes
	 * @return
	 */
	public static double getAverageStatusGraphMetricValue(Graph<Actor, Link> graph, GraphMetric type, Status status)
	{
		Set<String> ids = getStatusNodes(graph, status);
		
		return new org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics<Actor, Link>().getAverageGraphMetricValueOfNodes(graph, type, ids);
	}
	
	/**
	 * get the subgraph nodes time series for the specified node metric 
	 * 
	 * 
	 */
	public static Hashtable<String, TreeMap<Date, Double>> getSubGraphStatusNodesTimeSeries(SparseMultigraph<Actor, Link> multiGraph, Set<Actor> subGraphVertices, Date startDate, Date endDate, int timeIncrement, int incrementMultiplicity, long timeLag, NodeMetric type, Status status)
	{
		try
		{
			org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics<Actor, Link> genericMetrics = new org.unige.mpej.eckmann.sonedyan.graph.GraphMetrics<Actor, Link>(); 
			Hashtable<String, TreeMap<Date, Double>> timeSeries = new Hashtable<String, TreeMap<Date, Double>>();
			
			// start at time: start date + timeLag
			long startTime = startDate.getTime() + timeLag;
			long endTime = endDate.getTime() - timeLag;
			long time = startTime;
			
			Calendar cal = Calendar.getInstance();
			cal.setTime(new Date(startTime));
			SimpleDateFormat formatter = new SimpleDateFormat(DataUtils.INPUT_DATE_FORMAT);
			
			// set of dates of the time series
			HashSet<Date> dates = new HashSet<Date>();
			
			log.debug("# sub graph vertices: " + subGraphVertices.size());
			
			double totalDensity = 0;
			int counter = 0;
			
			while (time <= endTime)
			{
				Graph<Actor, Link> snapshotGraph = GraphUtils.getMultiGraphSnapshot(multiGraph, time, timeLag);
				// get the set of ids having the specified status at the snapshot time
				Set<String> ids = getStatusNodes(snapshotGraph, status);
				
				log.debug("Extracted snapshot (#nodes '" + snapshotGraph.getVertexCount() + "', #edges '" + snapshotGraph.getEdgeCount() + "') for date: " + formatter.format(new Date(time)));
				log.debug("# '" + status + "' vertices found in the snapshot: " + ids.size());
				
				Graph<Actor, Link> snapshotSubGraph = GraphUtils.getSubGraph2(snapshotGraph, subGraphVertices);
				
				int vertexCount = snapshotSubGraph.getVertexCount();
				int edgeCount = snapshotSubGraph.getEdgeCount();
				double density = 0;
				
				if (edgeCount > 0)
				{
					density = GraphUtils.getDensity(snapshotSubGraph);
					totalDensity += density;
					counter++;
				}
				
				log.debug("Extracted snapshot subgraph (#nodes '" + vertexCount + "', #edges '" + edgeCount + "', density '" + density + "')");
				log.debug("----------");
				
				TreeMap<Date, Double> nodeTimeSeries = null;
				
				for (Actor v: subGraphVertices)
				{
					String id = v.getId();
					
					// if the node has the right status
					if (ids.contains(id))
					{
						nodeTimeSeries = timeSeries.get(id);
						
						if (nodeTimeSeries == null){ nodeTimeSeries = new TreeMap<Date, Double>(); }
						
						double value = genericMetrics.getNodeMetric(snapshotSubGraph, id, type);
						
						nodeTimeSeries.put(new Date(time), value);
						timeSeries.put(id, nodeTimeSeries);
					}
				}
				
				dates.add(new Date(time));
				
				cal.add(timeIncrement, incrementMultiplicity);
				time = cal.getTime().getTime();
			}
			
			// complete missing values with '0' in the node time series
			Set<String> nodeIds = timeSeries.keySet();
			
			// for each node time series
			for (String id : nodeIds)
			{
				TreeMap<Date, Double> nodeSeries = timeSeries.get(id);
				
				for (Date d : dates)
				{
					Double value = nodeSeries.get(d);
					
					if (value == null)
					{
						nodeSeries.put(d, 0.0);
					}
				}
				
				timeSeries.put(id, nodeSeries);
			}
			
			log.debug("Average snapshots subgraphs density: " + (totalDensity / counter));
			
			return timeSeries;
		}
		catch(Exception e)
		{
			log.error("Unable to get nodes time series: " + e.getMessage());
			return null;
		}
	}
	
}
